﻿using System;
using Modbus.Messages;

namespace Modbus.Transport
{
	internal abstract class ModbusTransport : IModbusClientTransport
	{
		private byte functionCode;

		#region ( IDisposable )

		public void Dispose()
		{
			this.Dispose(true);	
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
		}

		#endregion

		#region ( IModbusClientTransport  )

		public void Request(byte slaveAddress, IModbusMessage message)
		{
			this.BeginTransaction();

			this.SendMessage(slaveAddress, message);

			this.EndTransaction();
		}

		public TResponse Request<TResponse>(byte slaveAddress, IModbusMessage message)
			where TResponse : IModbusMessage
		{
			this.functionCode = message.FunctionCode;

			this.BeginTransaction();

			this.SendMessage(slaveAddress, message);
			TResponse response = this.ReceiveResponse<TResponse>();

			this.EndTransaction();

			return response;
		}

		#endregion

		#region ( =>.<= )

		private void SendMessage(byte slaveAddress, IModbusMessage message)
		{
			ModbusError error;
			this.Send(slaveAddress, message, out error);

			if (error != ModbusError.Success)
			{
				throw new ModbusException(error);
			}
		}

		private TResponse ReceiveResponse<TResponse>()
			where TResponse : IModbusMessage
		{
			ModbusError error;
			ISomeObject obj = this.Receive(out error);

			if (error != ModbusError.Success)
			{
				throw new ModbusException(error);
			}

			//
			//
			//

			this.MatchResponse();

			byte functionCode = obj.FunctionCode;

			if (this.functionCode != FunctionCodes.CleanException(functionCode))
			{
				throw new MismatchResponseException(SR.ModbusMessageField_FunctionCode);
			}

			//
			//
			//

			if (FunctionCodes.IsException(functionCode))
			{
				var slaveException = obj.GetMessage<SlaveExceptionResponse>();

				throw new ModbusSlaveException(functionCode, slaveException.ExceptionCode);
			}

			return obj.GetMessage<TResponse>();
		}

		#endregion

		protected virtual void MatchResponse()
		{
		}

		protected virtual void BeginTransaction()
		{
		}

		protected virtual void EndTransaction()
		{
		}

		protected abstract void Send(byte slaveAddress, IModbusMessage message, out ModbusError errorCode);

		protected abstract ISomeObject Receive(out ModbusError errorCode);
	}
}
